package com.borened.common.boot.interceptor;

import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import com.borened.common.boot.security.RequireRole;
import com.borened.common.core.base.result.R;
import com.borened.common.core.base.result.ResultCode;
import com.borened.common.core.constant.SecurityConstants;
import com.borened.common.core.security.InnerAuth;
import com.borened.common.core.security.RequestSource;
import com.borened.common.core.util.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * jwt 认证拦截器，预防其他来源的接口攻击，添加此token拦截器。
 * <pre>
 * 1. 对于正常从网关转发过来的请求，默认认为是已登录并通过鉴权的用户。(携带了有效payload头可以通过)
 * 2. 对于内部feign调用的请求，某些可以不携带认证信息，例如用户认证过程的内部请求。这些请求都会通过注解@InnerAuth标识。
 * </pre>
 *
 * @author: cch
 * @since: 2022/1/11
 */
@Slf4j
public class JwtInterceptor implements HandlerInterceptor {

    private static boolean roleValidate(HandlerMethod handlerMethod) {
        //方法注解
        RequireRole methodAnnotation = handlerMethod.getMethodAnnotation(RequireRole.class);
        //类注解
        RequireRole classAnnotation = handlerMethod.getBean().getClass().getAnnotation(RequireRole.class);
        boolean result = true;
        if (methodAnnotation != null) {
            result = methodAnnotation.roleCode().equalsIgnoreCase(JwtUtils.getRole());
        } else if (classAnnotation != null) {
            result = classAnnotation.roleCode().equalsIgnoreCase(JwtUtils.getRole());
        }
        return result;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        ResultCode resultCode;
        try {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            InnerAuth innerAuth = handlerMethod.getMethodAnnotation(InnerAuth.class);
            //1. 内部调用跳过认证
            if (innerAuth != null && !innerAuth.checkToken()) {
                String requestSource = request.getHeader(SecurityConstants.REQUEST_SOURCE);
                //确认是内部认证调用 则跳过token认证检查。
                if (StrUtil.equalsIgnoreCase(requestSource, RequestSource.INNER.getValue())) {
                    return true;
                }
            }
            //2. 角色验证
            if (!roleValidate(handlerMethod)) {
                resultCode = ResultCode.USER_PERMS_LACK;
                ServletUtil.write(response, JSONUtil.toJsonStr(R.fail(resultCode)), "application/json;charset=utf-8");
                return false;
            }
            //3.其余所有请求需要验证token
            Long userId = JwtUtils.getUserId();
            return userId != null;
        } catch (ClassCastException e) {     //接口映射错误 404处理
            log.error("handlerMethod匹配失败！资源不存在,请求路径:{},请求参数:{}", request.getRequestURL(), JSONUtil.toJsonStr(request.getParameterMap()));
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            resultCode = ResultCode.RESOURCE_NOT_FOUND;
        } catch (Exception e) {  //TokenParseException 处理
            log.error("认证失败！非网关正常转发请求。请求路径:{},请求参数:{}", request.getRequestURL(), JSONUtil.toJsonStr(request.getParameterMap()));
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            resultCode = ResultCode.USER_NOT_LOGIN;
        }
        ServletUtil.write(response, JSONUtil.toJsonStr(R.fail(resultCode)), "application/json;charset=utf-8");
        return false;
    }

}
